查看原文
其他

【第1588期】彻底终结 Javascript 背后的隐式类型转换

chunpu 前端早读课 2019-09-13

前言

一个下雨的周末。今日早读文章由幂律智能@chunpu授权分享。

正文从这开始~~

网上已经有很多 JS 隐式类型转换相关的博客, 很多面试者专门复习过此问题, 但依然挡不住面试官一个又一个的无聊小题目~

[] == false // true !![] == true // true !!'' == false // true [1] == '1' // true '' == 0 // true '' == false // true [1] == true // true null == 0 // false null == '' // false

如果你还在为这些面试题烦恼, 那恭喜你来对了地方!

看完此文你可以完全不需要背诵复杂冗长的 ECMA 规范, 用逻辑即可推理

现在, 我们假装从 JS 设计者的角度来聊聊隐式类型转换, 首先要记住, JS 作者的”初衷”是美好的, 他希望 == 是最顺手最快捷的比较

为什么 [] == false ?

首先我们知道 [] 和 false 一个是对象, 一个是布尔值, 类型不同, 需要类型转换再做比较

要注意, JS 中规定, 如果 == 中有布尔值, 只能转换为数字, 那为什么不是转换成字符串呢?

因为如果布尔值转换成字符串那就是 ‘true’ 和 ‘false’, 那这种对比就毫无意义了

Number(true) // 1 Number(false) // 0

这也符合我们的常识, 很多语言也是类似的设定, 也就是 Bool 属于一种 Int

所以此问题可以转换成: 为什么 [] == 0?

为什么 [] == 0 ?

我们知道 Primitive(原值)和非 Primitive 比较, 需要把非 Primitive 转换成 Primitive 才可以

[] 是一个对象, 因此需要 toPrimitive()

简单的说, 大部分对象最后都是用 toString() 来转换成 Primitive

此处没聊 toPrimitive() 中的 valueOf() 单纯是因为面试题很少涉及

你也许会问为啥是用 toString 不是 toNumber 之类的呢? 因为每个对象都有 toString 方法, Object.prototype.toString, 更上层的对象也会重写 toString 方法

继续刨根问底, 为啥每个对象都有 toString 而不是 toNumber 呢?

这就是 JS 对新人友好的地方, JS 的对象都可以打印输出, 自带人性化展示, 在终端上人性化展示, 那当然是用字符串啦, 因此选择用 toString 转换 Primitive 理所因当

我们来看看数组的 toString, 数组的 toString 相当于 join()

[].toString() // '' [1].toString() // '1' [1, 2, 3, 4].toString() // '1,2,3,4'

所以此问题可以转换成: 为什么 ‘’ == 0 ?

为了验证我们的想法, 我们来尝试一些更奇葩的对象和字符串的 == 比较

[1] == '1' // true '[object Object]' == {} // true

({}).toString() 是 [object Object], 所以 ‘[object Object]’ == {} 果然也是 true

为什么 ‘’ == 0 ?

字符串和数字比较会把字符串转换成数字

问题来了, 为什么不是把数字转换成字符串呢? 从设计者的角度可能会这样想

都转成数字能处理的复杂场景更多, 容错性更高!

' 1 ' == 1.0 // true '12.10' == 12.1 // true

这样对开发者就会很方便

要注意, 字符串转成数字不是用的 parseInt() 或者 parseFloat(), 而是 Number()

Number('') // 0 Number('abc') // NaN

所以此问题可以继续转换成: 为什么 0 == 0? 显然就是返回 true

转换路程

推理到此结束, 我们回顾一下这个比较的转换规程

  1. [] == false

  2. [] == 0

  3. ‘’ == 0

  4. 0 == 0


看到这里我们猛然想明白了为啥 NaN 不能等于自身!

为什么 NaN !== NaN?

我们不妨来看看 ‘abc’ == NaN 做比较的过程

因为 NaN 也是数字类型, 所以我们需要把 ‘abc’ 转换为数字

‘abc’ == NaN 相当于 Number(‘abc’) == NaN 相当于 NaN == NaN, 如果 NaN 可以等于自身的话, 这种情况就会返回 true

那整个隐式转换就乱套了

因此 NaN 不能等于自身也是哑巴吃黄连, 有苦说不出啊

以上纯属本人推测, 概不负责, 毕竟 Java 中的 NaN 也不能等于自身

隐式转换的恶果

NaN 不能等于自身是隐式转换最大的恶果

你可以尝试如下操作

[1, 2, NaN].indexOf(NaN) // -1 [1, 2, NaN].includes(NaN) // true

有比较的地方, 就会有 NaN 特殊处理, 否则就是不严谨

简单逻辑复杂化, 说的就是你 NaN, 可以说 “隐式转换一时爽”~

为什么 null == 0 是 false 呢 ?

两边类型不同, 是不是也要类型转换呢?

要是能转的话确实要转, 但 null 和数字0本身已经是 Primitive 了, 没有机会再走一遍 toPrimitive(), 因此等号两边始终无法转换成同类型, 只能返回 false

为什么 null == undefined ?

和上面的问题一样, null 和 undefined 都是 Primitive, 而且也不是字符串或者数字, 转无可转

但 JS 专门规定了 null == undefined 就是返回 true, 属于一种专门的特殊情况

The Abstract Equality Comparison Algorithm

If x is null and y is undefined, return true.
If x is undefined and y is null, return true.

为什么 !![] 是 true?

这里面不涉及任何 == 比较, 和上面的题目完全是两类题目, 千万不可搞混

此题直接判断这个值是不是 Falsy(假值) 即可, 只要不是这几个值, 都是 true

Falsy 的值有 0, ‘’, false, NaN, null, undefined

类似的问题 !![] == true, 因为这个表达式先要计算 !![], 它已经是 true 了

为什么 ESLint 中会各种限制使用 ==?

我觉得完全可以理解, == 虽然也是一种便捷的转换, 但并不符合传统语言的习惯, 工程化企业化的项目不想用这种 “黑魔法” 也是一种正确的选择

关于本文
作者:@chunpu
原文:https://github.com/chunpu/blog/issues/104

为你推荐


【第1191期】你所忽略的js隐式转换


【第1506期】JavaScript工程项目的一系列最佳实践策略

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存